package com.linkoog.devtools.code_generator.template.loader;

import com.linkoog.devtools.code_generator.template.model.ContainerNodeData;
import com.linkoog.devtools.code_generator.template.model.NodeData;
import com.linkoog.devtools.code_generator.template.model.NodeDataFactory;
import com.linkoog.devtools.code_generator.template.model.node.DirNodeData;
import com.linkoog.devtools.code_generator.template.model.node.FileNodeData;
import com.linkoog.devtools.code_generator.template.model.node.TemplateNodeData;
import com.linkoog.devtools.code_generator.template.render.engine.TemplateType;
import com.linkoog.devtools.utils.IOUtils;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public interface TemplateLoader {

    TemplateNodeData loadTemplate(TemplateType templateEngineType,String templateName, String resourcePath);

    public static TemplateNodeData fileToTemplate(TemplateType templateEngineType, @NotNull String templateName, Path sourcePath, Predicate<Path> filter, String perfixToTrim, Charset charset) throws IOException{
        TreeMap<String, String> dataMap = fileToBuildTemplateData(sourcePath, filter, perfixToTrim, charset);
        return buildTemplate(templateEngineType, templateName, dataMap);
    }

    public static TemplateNodeData zipToTemplate(TemplateType templateEngineType, @NotNull String templateName, InputStream inputStream, Predicate<ZipEntry> filter, String perfixToTrim, Charset charset) throws IOException{
        TreeMap<String, String> dataMap = zipToBuildTemplateData(inputStream, filter, perfixToTrim, charset);
        return buildTemplate(templateEngineType, templateName, dataMap);
    }

    public static TreeMap<String, String> fileToBuildTemplateData(Path sourcePath, Predicate<Path> filter, String perfixToTrim)throws IOException{
        return fileToBuildTemplateData(sourcePath, filter, perfixToTrim, StandardCharsets.UTF_8);
    }

    public static TreeMap<String, String> fileToBuildTemplateData(Path sourcePath, Predicate<Path> filter, String perfixToTrim, Charset charset) throws IOException{
        if (charset == null){
            charset = StandardCharsets.UTF_8;
        }

        Charset finalCharset = charset;
        TreeMap<String, String> dataMap = new TreeMap<>();
        Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path relativePath = sourcePath.relativize(file);
                String relativePathString = relativePath.toString();
                if (relativePathString.isEmpty()){
                    dataMap.put(file.getFileName().toString(), readToString(Files.newInputStream(file), finalCharset));
                    return FileVisitResult.CONTINUE;
                }

                if (filter != null && !filter.test(relativePath)) {
                    return FileVisitResult.CONTINUE;
                }

                String path = trimPerfix(relativePathString, perfixToTrim);
                dataMap.put(path,  readToString(Files.newInputStream(file), finalCharset));
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Path relativePath = sourcePath.relativize(dir);
                String relativePathString = relativePath.toString();
                if (relativePathString.isEmpty()){
                    return FileVisitResult.CONTINUE;
                }

                if (filter != null && !filter.test(relativePath)) {
                    return FileVisitResult.SKIP_SUBTREE;
                }

                String path = trimPerfix(relativePathString + "/", perfixToTrim);
                dataMap.put(path,  null);
                return FileVisitResult.CONTINUE;
            }
        });
        return dataMap;
    }

    public static TreeMap<String, String> zipToBuildTemplateData(InputStream inputStream, Predicate<ZipEntry> filter, String perfixToTrim) throws IOException{
        return zipToBuildTemplateData(inputStream, filter, perfixToTrim, StandardCharsets.UTF_8);
    }

    public static TreeMap<String, String> zipToBuildTemplateData(InputStream inputStream, Predicate<ZipEntry> filter, String perfixToTrim, Charset charset) throws IOException{
        if (charset == null){
            charset = StandardCharsets.UTF_8;
        }

        TreeMap<String, String> dataMap = new TreeMap<>();
        try (ZipInputStream zipInputStream = new ZipInputStream(getBufferedInputStream(inputStream), charset)){
            ZipEntry zipEntry;
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                String zipEntryName = zipEntry.getName();
                if (filter != null && !filter.test(zipEntry)) continue;

                String path = trimPerfix(zipEntryName, perfixToTrim);
                if (zipEntry.isDirectory()){
                    dataMap.put(path, null);
                } else {
                    dataMap.put(path, readToString(zipInputStream, charset));
                }
            }
        }
        return dataMap;
    }

    public static TemplateNodeData buildTemplate(TemplateType templateEngineType, @NotNull String templateName, TreeMap<String, String> dataMap){
        if (dataMap == null || dataMap.isEmpty()) return null;

        TemplateNodeData templateNodeData = NodeDataFactory.createTemplateNodeData(templateName, templateEngineType);
        Map<String, DirNodeData> temp = new HashMap<>();
        for (Map.Entry<String, String> entry : dataMap.entrySet()) {
            String path = entry.getKey();
            String value = entry.getValue();

            NodeData nodeData = null;
            String name = getName(path);
            if (value != null){
                //文件
                FileNodeData fileNodeData = new FileNodeData(name, "vm");
                fileNodeData.setTemplate(value);
                nodeData = fileNodeData;
            } else {
                //文件夹
                DirNodeData dirNodeData = new DirNodeData(name.isBlank()? templateName : name);
                temp.put(path, dirNodeData);
                nodeData = dirNodeData;
            }

            String parentPath = getParentPath(path);
            if (parentPath == null){
                //zip根目录，添加到templateNodeData中
                templateNodeData.addChildNode(nodeData);
                continue;
            }

            DirNodeData parentNodeData = temp.get(parentPath);
            if (parentNodeData != null){
                parentNodeData.addChildNode(nodeData);
            }
        }

        // 当模板名称与第一层文件夹相同时，去除第一层文件夹
        List<NodeData> nodeDataList = templateNodeData.getChildNodeList();
        for (int i = 0; i < nodeDataList.size(); i++) {
            NodeData nodeData = nodeDataList.get(i);
            if (templateName.equals(nodeData.getName()) && nodeData instanceof ContainerNodeData){
                templateNodeData.removeChildNode(i);
                templateNodeData.addChildNode(((ContainerNodeData)nodeData).getChildNodeList());
                break;
            }
        }

        return templateNodeData;
    }





    private static String trimPerfix(String source, String perfixToTrim){
        if (source == null || source.isEmpty()) return source;
        if (perfixToTrim == null || perfixToTrim.isEmpty()) return source;

        if (source.startsWith(perfixToTrim)){
            return source.substring(perfixToTrim.length() - 1);
        }
        return source;
    }

    private static BufferedInputStream getBufferedInputStream(InputStream inputStream){
        if (inputStream == null) return null;
        if (inputStream instanceof BufferedInputStream) return (BufferedInputStream)inputStream;
        return new BufferedInputStream(inputStream);
    }

    /**
     * 读取 InputStream 到 String字符串中
     */
    private static String readToString(InputStream in, Charset charset) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()){
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = in.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            return baos.toString(charset);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    private static String getName(String name){
        if (name.endsWith("/")){
            name = name.substring(0,  name.length() -1);
        }

        int index = name.lastIndexOf("/");
        if (index == -1){
            return name;
        } else {
            return name.substring(index + 1);
        }
    }

    private static String getParentPath(String name){
        if (name.endsWith("/")){
            name = name.substring(0,  name.length() -1);
        }

        int index = name.lastIndexOf("/");
        if (index == -1){
            return null;
        } else {
            return name.substring(0,  index + 1);
        }
    }

}
